/*
 * OpfMetadataReader.java
 *
 *  created: 4.9.2011
 *  charset: UTF-8
 *  license: MIT (X11) (See LICENSE file for full license)
 */
package cz.mp.k3bg.core;

import cz.mp.k3bg.Application;
import cz.mp.k3bg.log.LoggerManager;
import cz.mp.util.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

/**
 * Třída {@code OpfMetadataReader} čte základní metadata o knize ve formátu 
 * {@code OPF} ve verzi 2.
 * <p>
 * viz <a href="http://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm>
 * Specifikace formátu OPF 2.0.1</a>
 * 
 * @author Martin Pokorný
 * @version 0.1
 * @see OpfGenerator
 * @see Metadata
 */
public class OpfMetadataReader {

    private static final boolean DEBUG = false;
    private static final Logger logger =
            LoggerManager.getLogger(OpfMetadataReader.class, DEBUG);
    
    private File opfFile;
    
    private Metadata readedMetadata;
    
    private ItemImage readedCoverImage;
    private boolean isCoverAsXml;
            
    // -----
    
    /** */
    public OpfMetadataReader(File opfFile) {
        setOpfFileImpl(opfFile);
    }
    
    // -----

    /**
     * 
     * @return 
     */
    public File getOpfFile() {
        return opfFile;
    }

    /**
     * 
     * @param opfFile 
     */
    private void setOpfFileImpl(File opfFile) {
        if (opfFile == null) {
            throw new IllegalArgumentException("opfFile=null");
        }
        this.opfFile = opfFile;
    }
    
    /**
     * 
     * @throws XMLStreamException
     * @throws IOException 
     */
    public void parse() 
            throws XMLStreamException, IOException {
        parseOpfFile();
    }
    
    private InputStream is;
    private XMLStreamReader xmlSr;
            
    /**
     * 
     * @throws XMLStreamException
     * @throws IOException 
     */
    public void close() 
            throws XMLStreamException, IOException {
        if (is != null) {
            is.close();
        }
        if (xmlSr != null) {
            xmlSr.close();
        }
    }
    
    /**
     * Parsuje zadaný {@code opf} soubor s použitím {@code StAX} parseru.
     */ 
    private void parseOpfFile() 
            throws FileNotFoundException, XMLStreamException, IOException {
        logger.info("");

        XMLInputFactory inputFactory = XMLInputFactory.newInstance();

        inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        inputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
        
        is = new FileInputStream(opfFile);
        
        // kurzorové rozhraní
        xmlSr = inputFactory.createXMLStreamReader(is);
//        String encoding = xmlSr.getEncoding();
        
        readedMetadata = new Metadata();
        
        boolean inMetadataTag = false;
        boolean inManifestTag = false;
        
        String dcNamespaceUri = null;
        String opfNamespaceUri = null;
        
        String coverId = "";
        String coverHref = "";
                
        while (xmlSr.hasNext()) {
            xmlSr.next();
            
            if (xmlSr.isStartElement()) {
                
                String elementName = xmlSr.getLocalName();
                String elementNamespaceUri = xmlSr.getNamespaceURI();
                logger.finest ("element = <" + elementName + ">");
                        
                if(elementName.equals("metadata")) {
                    inMetadataTag = true;

                    if (xmlSr.getNamespaceCount() == 2) {
                        dcNamespaceUri = xmlSr.getNamespaceURI("dc");
                        opfNamespaceUri = xmlSr.getNamespaceURI("opf");
                        
                        logger.finer("metadata dcNamespaceUri = " + dcNamespaceUri);
                        logger.finer("metadata opfNamespaceUri = " + opfNamespaceUri);
                    }
                }   // metadata
                
                // kvůli starším verzím OPF se dcNamespaceUri nepoužije
                if (inMetadataTag) {
                    
                    if (elementName.equalsIgnoreCase("title")) {
                        String text = xmlSr.getElementText();
                        if (! text.isEmpty()) {
                            readedMetadata.setTitle(text.trim());
                            logger.fine("title = " + text);
                        }                                                
                    }   // title
                    
                    if (elementName.equalsIgnoreCase("creator")) {
                        String role = xmlSr.getAttributeValue(
                                opfNamespaceUri, "role");
                        String text = xmlSr.getElementText();
                        if (! text.isEmpty()) {                                                    
                            // Autor buď jako Autor, nebo jako první creator
                            if ("aut".equalsIgnoreCase(role)) {  // Autor ! 
                                readedMetadata.setAuthor(text.trim());
                                logger.fine("author = " + text);
                            }
                            else if (StringUtils.isBlank(
                                    readedMetadata.getAuthor())) {
                                readedMetadata.setAuthor(text.trim());
                                logger.fine("author = " + text);
                            }
                        }                        
                    }   // creator
                    
                    if (elementName.equalsIgnoreCase("language")) {
                        String text = xmlSr.getElementText();
                        if (! StringUtils.isBlank(text)) {
                            readedMetadata.setLang(text.trim());
                            logger.fine("language = " + text);
                        }                        
                    }   // language
                    
                    if (elementName.equalsIgnoreCase("subject")) {
                        readedMetadata.setGenre(
                                xmlSr.getElementText().trim());                        
                    }   // subject
                    
                    if (elementName.equalsIgnoreCase("publisher")) {
                        readedMetadata.setPublisher(
                                xmlSr.getElementText().trim());
                    }   // publisher
                    
                    if (elementName.equalsIgnoreCase("date")) {
                        readedMetadata.setPublishDate(
                                xmlSr.getElementText().trim());
                    }   // date
                    
                    if (elementName.equalsIgnoreCase("description")) {
                        // v description bývají HTML tagy (jako <p>,<i> apod.);
                        //  to je ale bohužel problém při zpracování XML s StAX
                        //  (StAX je bere za XML tagy).
                        //  Je použit "hack", který "využívá" XMLStreamException
                        //  Hack ale stejně selže u nepárových tagů jako je <br>
                        
                        StringBuilder sb = new StringBuilder();
                        
                        try {
                            sb.append(xmlSr.getElementText().trim());
                        } catch (XMLStreamException stex) {
                            boolean first = true;
                            while (xmlSr.hasNext()) {
                                if (!first) {
                                    xmlSr.next();
                                }
                                if (xmlSr.isStartElement()) {
                                    sb.append(xmlSr.getElementText());
                                    sb.append(Application.EOL);
                                }
                                else if (xmlSr.isEndElement() && 
                                        xmlSr.getLocalName()
                                            .equalsIgnoreCase("description")) {
                                    break;
                                }
                                first = false;
                            }
                        }
                        
                        readedMetadata.setDescription(sb.toString());
                    }   // description
                    
                    if (elementName.equalsIgnoreCase("identifier")) {
                        String scheme = xmlSr.getAttributeValue(
                                opfNamespaceUri, "scheme");
                        String text = xmlSr.getElementText();
                        if (! text.isEmpty()) {                          
                            readedMetadata.setIdIsbn(false);                            
                            if ("ISBN".equalsIgnoreCase(scheme)) {
                                readedMetadata.setIdIsbn(true);
                                logger.fine("isbn = " + text);
                            }
                            else {
                                logger.fine("id = " + text);
                            }
                            readedMetadata.setId(text.trim());
                        }
                    }   // identifier
                    
//                }   // inMetadataTag                
//                else if (inMetadataTag) {
                    
                    // <meta name="cover" content="coverArt" />
                    if (elementName.equalsIgnoreCase("meta")) {                        
                        String name = xmlSr.getAttributeValue(null, "name");
                        logger.finer("    meta name  = " + name);
                        if (name.equalsIgnoreCase("cover")) {
                            coverId = xmlSr.getAttributeValue(null, "content");
                            logger.finer("    coverId = " + coverId);
                        }
                    }
                }   // inMetadataTag
                
                if (coverId != null && ! coverId.isEmpty() &&
                        elementName.equalsIgnoreCase("manifest")) {
                    inManifestTag = true;
                } 
                else if (inManifestTag &&
                        elementName.equalsIgnoreCase("item")) {
                    // <item id="coverArt" media-type="image/jpeg" href="Wetemaa.jpg" />
                    String itemId = xmlSr.getAttributeValue(null, "id");
                    logger.finest("    id = " + itemId);
                    if (itemId.equals(coverId)) {
                        coverHref = xmlSr.getAttributeValue(null, "href");
                            
                        String mimeType = 
                                xmlSr.getAttributeValue(null, "media-type");
                        if (MimeType.XHTML.getValue().equals(mimeType)) {
                            logger.fine("XML cover !");
                            isCoverAsXml = true;
                        }
                    }
                }
            }   // if (xmlSr.isStartElement())
            
            if (xmlSr.isEndElement()) {
                
                if(xmlSr.getLocalName().equals("metadata")) {
                    logger.finest ("</metadata>");
                    inMetadataTag = false;
                }
                
                if(xmlSr.getLocalName().equals("manifest")) {
                    logger.finest ("</manifest>");
                    inManifestTag = false;
                }                
            }   // if (xmlSr.isEndElement())
            
        }
        
        close();
        
        if (coverHref != null && ! coverHref.isEmpty()) {
            readedCoverImage = new ItemImage(coverHref);
            logger.fine("cover = " + coverHref);
        }
        else {
            logger.fine("no cover");
        }
        
        logger.fine("finished!");

    }

    /**
     * 
     * @return 
     */
    public Metadata getReadedMetadata() {
        return readedMetadata;
    }

    /**
     * 
     * @return 
     */
    public ItemImage getReadedCoverImage() {
        return readedCoverImage;
    }
    
    /**
     * 
     * @return 
     */
    public boolean isCoverAsXml() {
        return isCoverAsXml;
    }    
    
}   // OpfMetadataReader
